// 编解码
package core

import (
	"fmt"
	"go-iot/app/common/logger"
	"github.com/dop251/goja"
)

func init() {
	RegCodecCreator(Script_Codec, func(productId, script string) (Codec, error) {
		core, err := NewScriptCodec(productId, script)
		return core, err
	})
}

const (
	OnConnect          = "OnConnect"
	OnMessage          = "OnMessage"
	OnInvoke           = "OnInvoke"
	On_Device_Deploy   = "OnDeviceDeploy"
	On_Device_UnDeploy = "OnDeviceUnDeploy"
	On_State_Checker   = "OnStateChecker"
)

// js脚本编解码
type ScriptCodec struct {
	script    string
	productId string
	pool      *VmPool
}

func NewScriptCodec(productId, script string) (Codec, error) {
	
	// 关闭旧VmPool
	oldCodec := GetCodec(productId)
	if c, ok := oldCodec.(*ScriptCodec); ok {
		c.pool.Close()
	}
	// 创建新的VmPool
	pool, err := NewVmPool(script, 20, productId)
	if err != nil {
		return nil, err
	}

	sc := &ScriptCodec{
		script:    script,
		productId: productId,
		pool:      pool,
	}

	RegCodec(productId, sc)
	RegDeviceLifeCycle(productId, sc)

	return sc, nil
}

// 设备连接时
func (c *ScriptCodec) OnConnect(ctx MessageContext) error {
	_, err := c.FuncInvoke(OnConnect, ctx)
	return err
}

// 接收消息
func (c *ScriptCodec) OnMessage(ctx MessageContext) error {
	_, err := c.FuncInvoke(OnMessage, ctx)
	return err
}

// 命令调用
func (c *ScriptCodec) OnInvoke(ctx FuncInvokeContext) error {
	_, err := c.FuncInvoke(OnInvoke, ctx)
	return err
}

// 连接关闭
func (c *ScriptCodec) OnClose(ctx MessageContext) error {
	return nil
}

// 设备新增
func (c *ScriptCodec) OnDeviceDeploy(ctx DeviceLifecycleContext) error {
	_, err := c.FuncInvoke(On_Device_Deploy, ctx)
	return err
}

// 设备修改
func (c *ScriptCodec) OnDeviceUnDeploy(ctx DeviceLifecycleContext) error {
	_, err := c.FuncInvoke(On_Device_UnDeploy, ctx)
	return err
}

// 状态检查
func (c *ScriptCodec) OnStateChecker(ctx DeviceLifecycleContext) (string, error) {
	resp, err := c.FuncInvoke(On_State_Checker, ctx)
	if resp != nil {
		return resp.String(), nil
	}
	return "", err
}

func (c *ScriptCodec) FuncInvoke(name string, param interface{}) (resp goja.Value, err error) {
	vm := c.pool.Get()
	defer c.pool.Put(vm)
	fn, success := goja.AssertFunction(vm.Get(name))
	if success {
		defer func() {
			if rec := recover(); rec != nil {
				l := fmt.Sprintf("productId: [%s] error: %v", c.productId, rec)
				logger.Errorf(l)
				deviceId := ""
				if ctx, ok := param.(DeviceLifecycleContext); ok && ctx.GetDevice() != nil {
					deviceId = ctx.GetDevice().Id
				}
				DebugLog(deviceId, c.productId, l)
				//logger.Errorf(string(debug.Stack()))
				err = fmt.Errorf("%v", rec)
				resp = goja.Undefined()
			}
		}()
		resp, err = fn(goja.Undefined(), vm.ToValue(param))
		if err != nil {
			logger.Errorf("productId: [%s], error: %v", c.productId, err)
			deviceId := ""
			if ctx, ok := param.(DeviceLifecycleContext); ok && ctx.GetDevice() != nil {
				deviceId = ctx.GetDevice().Id
			}
			DebugLog(deviceId, c.productId, err.Error())
		}
		return resp, err
	}
	return nil, ErrFunctionNotImpl
}
